﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Net.Sockets;
using System.IO;
using System.Net;

namespace MessageMonitor
{
    /// <summary>
    /// class of Monitor, the main class.
    /// </summary>
    public partial class Monitor : Form
    {
        string rule;
        AC ac;
        IPEndPoint broadcastEndPoint = new IPEndPoint(IPAddress.Broadcast, 50317);
        /// <summary>
        /// Construct function of Monitor.
        /// </summary>
        public Monitor()
        {
            InitializeComponent();
            Control.CheckForIllegalCrossThreadCalls = false;
        }
        /// <summary>
        /// Get the filtering rule.
        /// </summary>
        public string Rule
        {
            get
            {
                return rule;
            }
        }
        /// <summary>
        /// Get Aho-Corasick automation of the filtering rule.
        /// </summary>
        public AC AC
        {
            get
            {
                return ac;
            }
        }
        /// <summary>
        /// Get IP and Port information for broadcast.
        /// </summary>
        public IPEndPoint BroadcastEndPoint
        {
            get
            {
                return broadcastEndPoint;
            }
        }
        /// <summary>
        /// Check if we should show the filtered message.
        /// </summary>
        public bool ShowFilteredMessage
        {
            get
            {
                return this.showFilteredMessage.Checked;
            }
        }

        /// <summary>
        /// Start a new screen to monitor a server.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Start_Click(object sender, EventArgs e)
        {
            IPAddress ip;
            int port;
            try
            {
                ip = IPAddress.Parse(IP.Text);
                port = int.Parse(portText.Text);
            }
            catch
            {
                MessageBox.Show("Please check IP and Port.");
                return;
            }
            ScreenForm newWatch = new ScreenForm(this, ip, port);
            newWatch.Show();
            newWatch.Start();
        }
        /// <summary>
        /// Use new filtering rule.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FilteringButton_Click(object sender, EventArgs e)
        {
            if (CanPassCheck(this.FilteringText.Text) == false)
            {
                return;
            }
            try
            {
                ac = new AC(this.FilteringText.Text);
                rule = this.FilteringText.Text;
            }
            catch
            {
                MessageBox.Show("Something is wrong with the rule, Please Check It!");
                rule = null;
            }
        }
        /// <summary>
        /// Check the rule if it is valid.
        /// </summary>
        /// <param name="s">The rule.</param>
        /// <returns>Returns true if it can pass the check, otherwise show the wrong message and returns false.</returns>
        private bool CanPassCheck(string s)
        {
            return PassBracketCheck(s) && PassQuotationCheck(s) && PassOperatorCheck(s);
        }
        /// <summary>
        /// Check the rule if it can pass operator check.
        /// </summary>
        /// <param name="s">The rule.</param>
        /// <returns>Returns true if it can pass the check, otherwise show the wrong message and returns false.</returns>
        private bool PassOperatorCheck(string s)
        {
            int cnt = 0;
            for (int i = 0; i < s.Length; ++i)
            {
                if (s[i] == '(' || s[i] == ')')
                {
                    continue;
                }
                if (s[i] == '"')
                {
                    ++cnt;
                }
                else if (cnt % 2 == 0 && s[i] != ' ')
                {
                    int j = i + 1;
                    while (j < s.Length && s[j] != ' ' && s[j] != '"')
                    {
                        ++j;
                    }
                    string op = s.Substring(i, j - i);
                    if (op != "and" && op != "or")
                    {
                        MessageBox.Show("Unrecognizable operator \"" + op + "\"!");
                        return false;
                    }
                    i = j - 1;
                }
            }
            return true;
        }
        /// <summary>
        /// Check the rule if it can pass quotation check.
        /// </summary>
        /// <param name="s">The rule.</param>
        /// <returns>Returns true if it can pass the check, otherwise show the wrong message and returns false.</returns>
        private bool PassQuotationCheck(string s)
        {
            int cnt = 0;
            foreach (char c in s)
            {
                if (c == '"')
                {
                    ++cnt;
                }
            }
            if (cnt % 2 == 1)
            {
                MessageBox.Show("Quotations are not matched!");
                return false;
            }
            return true;
        }
        /// <summary>
        /// Check the rule if it can pass quotation check.
        /// </summary>
        /// <param name="s">The rule.</param>
        /// <returns>Returns true if it can pass the check, otherwise show the wrong message and returns false.</returns>
        private bool PassBracketCheck(string s)
        {
            Stack<char> sta = new Stack<char>();
            int cnt = 0;
            foreach (char c in s)
            {
                if (c == '"')
                {
                    ++cnt;
                }
                if (cnt % 2 != 0)
                {
                    continue;
                }
                if (c == '(')
                {
                    sta.Push(c);
                }
                else if (c == ')')
                {
                    if (sta.Count == 0 || sta.Peek() != '(')
                    {
                        MessageBox.Show("Brackets are not matched!");
                        return false;
                    }
                    else
                    {
                        sta.Pop();
                    }
                }
            }
            return true;
        }
        /// <summary>
        /// Set new broadcast information.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void broadcastButton_Click(object sender, EventArgs e)
        {
            try
            {
                string[] s = this.broadcastText.Text.Split(':');
                this.broadcastEndPoint = new IPEndPoint(IPAddress.Parse(s[0]), int.Parse(s[1]));
            }
            catch
            {
                MessageBox.Show("Broadcast setting ERROR");
            }
        }
    }
    /// <summary>
    /// Class of Aho-Corasick automation.
    /// </summary>
    public class AC
    {
        /// <summary>
        /// Datastruture of Aho-Corasick automation.
        /// </summary>
        class Str
        {
            public Dictionary<char, int> g = new Dictionary<char, int>();
            public int f;
            public HashSet<string> have = new HashSet<string>();
        }
        /// <summary>
        /// Heap pool.
        /// </summary>
        List<Str> nt = new List<Str>();
        /// <summary>
        /// Character set.
        /// </summary>
        HashSet<char> ini;
        /// <summary>
        /// Construct function of Aho-Corasick automation.
        /// </summary>
        /// <param name="rule">Filtering rule.</param>
        public AC(string rule)
        {
            ini = new HashSet<char>();
            nt.Add(new Str());
            for (int i = 0; i < rule.Length; ++i)
            {
                ini.Add(rule[i]);
                if (rule[i] == '"')
                {
                    int j = i + 1;
                    while (j < rule.Length && rule[j] != '"')
                    {
                        ++j;
                    }
                    insert(rule.Substring(i + 1, j - i - 1));
                    i = j;
                }
            }
            CreateAC();
        }
        /// <summary>
        /// Create Aho-Corasick automation after all the words have been inserted.
        /// </summary>
        void CreateAC()
        {
            Queue<int> Q = new Queue<int>();
            foreach (char c in ini)
            {
                if (nt[0].g.ContainsKey(c))
                {
                    nt[nt[0].g[c]].f = 0;
                    Q.Enqueue(nt[0].g[c]);
                }
                else
                {
                    nt[0].g[c] = 0;
                }
            }
            while (Q.Count() != 0)
            {
                int r = Q.Dequeue();
                foreach (KeyValuePair<char, int> c in nt[r].g)
                {
                    int u = c.Value;
                    Q.Enqueue(u);
                    int v = nt[r].f;
                    while (nt[v].g.ContainsKey(c.Key) == false)
                    {
                        v = nt[v].f;
                    }
                    nt[u].f = nt[v].g[c.Key];
                    nt[u].have.UnionWith(nt[nt[u].f].have);
                }
            }
        }
        /// <summary>
        /// Insert a new word
        /// </summary>
        /// <param name="s">The word.</param>
        void insert(string s)
        {
            if (s.Length == 0)
            {
                return;
            }
            int i = 0;
            foreach (char c in s)
            {
                if (!nt[i].g.ContainsKey(c))
                {
                    nt[i].g[c] = nt.Count;
                    nt.Add(new Str());
                }
                i = nt[i].g[c];
            }
            nt[i].have.Add(s);
        }
        /// <summary>
        /// Find all the word where our Aho-Corasick automation contains.
        /// </summary>
        /// <param name="s">The message.</param>
        /// <returns>Return a HashSet with all the words which are substrings of param s and where our Aho-Corasick automation contains.</returns>
        internal HashSet<string> GetWhich(string s)
        {
            HashSet<String> res = new HashSet<string>();
            int i = 0;
            foreach (char c in s)
            {
                while (i != 0 && nt[i].g.ContainsKey(c) == false)
                {
                    i = nt[i].f;
                }
                if (nt[i].g.ContainsKey(c))
                {
                    i = nt[i].g[c];
                }
                foreach (string t in nt[i].have)
                {
                    res.Add(t);
                }
            }
            return res;
        }
        /// <summary>
        /// Hightlight the keyword.
        /// </summary>
        /// <param name="len">Offset value</param>
        /// <param name="s">The message.</param>
        /// <param name="textBox">The text box which should be hightlight.</param>
        public void Highlight(int len, string s, System.Windows.Forms.RichTextBox textBox)
        {
            int i = 0;
            for (int c = 0; c < s.Length; ++c)
            {
                while (i != 0 && nt[i].g.ContainsKey(s[c]) == false)
                {
                    i = nt[i].f;
                }
                if (nt[i].g.ContainsKey(s[c]))
                {
                    i = nt[i].g[s[c]];
                }
                foreach (string t in nt[i].have)
                {
                    textBox.Select(len + c - t.Length + 1, t.Length);
                    textBox.SelectionColor = Color.Red;
                    textBox.SelectionStart = int.MaxValue;
                }
            }
        }
    }
}
